﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.Serialization;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using HovisPresent.Net;

namespace HovisPresent
{
	public enum PresenterWindowLayout { ScriptBottom, ScriptLeft };


	public partial class MainWindow : Form
	{
		public HovisDocument Document {get;set;}
		ViewForm presenterWindow;
		ViewForm presentationWindow;
		public int SlideNumber { get; set; }
		public PresenterWindowLayout PresenterLayout { get; set; }
		public System.Drawing.Font ScriptFont { get; set; }
		private string Title = "Hovis Presenter";
		public OpeningForm OpeningForm { get; set; }
		private Size thumbSize;
		RunPresentationOptions RunPresentationOptions = new RunPresentationOptions();

		/// <summary>
		/// A list of images to load in the background.
		/// </summary>
		private List<ListViewItem> ImagesToLoad = new List<ListViewItem>();
		/// <summary>
		/// The item currently having its image loaded in the background
		/// </summary>
		private ListViewItem BackgroundImageToLoad = null;

		LastSlide lastSlide = null;

		SlaveController slaveController = null;
		ServerConnection serverConnection = null;

		/// <summary>
		/// have we added the presenter window video player message event? 
		/// </summary>
		private bool presenterWindowPlayerMessageEventAdded = false;

		// have we checked the screen power saving info?
		private bool ScreenPowerSavingChecked = false;
		private bool ScreenSaverEnabled;
		private bool ScreenLowPowerEnabled;
		private bool ScreenPowerOffEnabled;

		public MainWindow() {
			InitializeComponent();
			RunPresentationOptions.Reload();
			PresenterLayout = PresenterWindowLayout.ScriptLeft;
			Document = new HovisDocument();
			ScriptFont = Properties.Settings.Default.ScriptFont;
			thumbSize = GetThumbnailSize(Properties.Settings.Default.ThumbnailSize);
		}

		/// <summary>
		/// Handles the Load event of the MainWindow control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void MainWindow_Load(object sender, EventArgs e) {
			GenerateMRU();
			SetTitle();
			CreateThumbImageList();
			list.ListViewItemSorter = (System.Collections.IComparer) new ListViewItemSortByIndex();
			Size size = Properties.Settings.Default.MainWindowSize;
			if (size != null)
				ClientSize = size;
		}

		/// <summary>
		/// Updates the script box with the slides script if one is selected,
		/// otherwise disable the script box
		/// </summary>
		private void updateScriptBox() {
			if (list.SelectedItems.Count == 1) {
				introScript.Enabled = script.Enabled = true;
				Slide slide = (Slide)list.SelectedItems[0].Tag;
				script.Text = slide.Script;
				introScript.Text = slide.IntroScript;
			} else {
				introScript.Enabled = script.Enabled = false;
				introScript.Text = script.Text = "";
			}
		}

		/// <summary>
		/// Handles the TextChanged event of the script control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void script_TextChanged(object sender, EventArgs e) {
			if (list.SelectedItems.Count != 1)
				return;
				
			Slide slide = (Slide)list.SelectedItems[0].Tag;
			if (slide.Script.CompareTo(script.Text) != 0) {
				slide.Script = script.Text;
				SetModified(true);
			}
		}

		/// <summary>
		/// Handles the TextChanged event of the introScript control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void introScript_TextChanged(object sender, EventArgs e) {
			if (list.SelectedItems.Count != 1)
				return;

			Slide slide = (Slide)list.SelectedItems[0].Tag;
			if (slide.IntroScript==null || slide.IntroScript.CompareTo(introScript.Text) != 0) {
				slide.IntroScript = introScript.Text;
				SetModified(true);
			}
		}

		/// <summary>
		/// Handles the LinkClicked event of the projectDirectory control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.Windows.Forms.LinkLabelLinkClickedEventArgs"/> instance containing the event data.</param>
		private void projectDirectory_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) {
			System.Diagnostics.ProcessStartInfo info = new System.Diagnostics.ProcessStartInfo(Document.Directory);
			info.UseShellExecute = true;
			info.Verb = "open";
			System.Diagnostics.Process.Start(info);
		}

		#region Slide List Functions
		private void list_ItemDrag(object sender, ItemDragEventArgs e) {
			list.DoDragDrop(list.SelectedItems, DragDropEffects.Move | DragDropEffects.None);
		}

		private void list_DragEnter(object sender, DragEventArgs e) {
			if (e.Data.GetDataPresent("System.Windows.Forms.ListView+SelectedListViewItemCollection"))
				e.Effect = DragDropEffects.Move;
			else if (e.Data.GetDataPresent("FileDrop"))
				e.Effect = DragDropEffects.Copy;
			else
				e.Effect = DragDropEffects.None;
		}

		private void list_DragDropFileDrop(object sender, DragEventArgs e) {
			string[] FileList = (string[])e.Data.GetData("FileDrop", false);

			bool hadDirectory = false;
			bool hadWrongDirectory = false;

			string rootDirectory = Document.Directory + "\\";

			var itemsToAdd = new List<String>();
			foreach (string filename in FileList) {
				if (Directory.Exists(filename)) {
					hadDirectory = true;
					break;
				}
				if (!File.Exists(filename))
					continue;

				if (String.Compare(filename, 0, rootDirectory, 0, rootDirectory.Length, false) != 0) {
					hadWrongDirectory = true;
					continue;
				}
				itemsToAdd.Add(filename);
			}

			Point cp = list.PointToClient(new Point(e.X, e.Y));
			ListViewItem dragToItem = list.GetItemAt(cp.X, cp.Y);
			int dragToIndex = dragToItem != null ? dragToItem.Index : -1;

			foreach (string f in itemsToAdd) {
				string relativeFilename = f.Substring(rootDirectory.Length);
				Slide newSlide = new Slide(relativeFilename);

				if (dragToIndex == -1) {
					Document.Slides.Add(newSlide);
					AddSlideToList(-1, newSlide);
				} else {
					Document.Slides.Insert(dragToIndex, newSlide);
					AddSlideToList(dragToIndex, newSlide);
					dragToIndex++;
				}
			}

			Debug.WriteLine("Finished File Drop");
			Document.DebugDump();

			SetModified(true);

			if (hadDirectory) {
				MessageBox.Show(this, "One or more of the items dropped was a directory and was not processed", "Error",
								MessageBoxButtons.OK, MessageBoxIcon.Information);
			}
			if (hadWrongDirectory) {
				MessageBox.Show(this, "One or more of the items dropped was not in the project directory or a subdirectory and was not processed", "Error",
								MessageBoxButtons.OK, MessageBoxIcon.Information);
			}
		}

		private void list_DragDrop(object sender, DragEventArgs e) {

			if (e.Data.GetDataPresent("FileDrop")) {
				list_DragDropFileDrop(sender, e);
				return;
			}

			if (list.InsertionPoint == -1)
				return;

			//Return if the items are not selected in the ListView control.
			if (list.SelectedItems.Count == 0) {
				return;
			}

			ListViewItem dragToItem = list.Items[list.InsertionPoint];
			list.InsertionPoint = -1;
			if (dragToItem == null) {
				return;
			}

			//Obtain the index of the item at the mouse pointer.
			int dragIndex = dragToItem.Index;
			ListViewItem[] sel = new ListViewItem[list.SelectedItems.Count];
			for (int i = 0; i <= list.SelectedItems.Count - 1; i++) {
				sel[i] = list.SelectedItems[i];
			}
			for (int i = 0; i < sel.GetLength(0); i++) {
				//Obtain the ListViewItem to be dragged to the target location.
				ListViewItem dragItem = sel[i];
				int itemIndex = dragIndex;
				if (itemIndex == dragItem.Index) {
					break;
				}
				if (dragItem.Index < itemIndex)
					itemIndex++;
				else
					itemIndex = dragIndex + i;
				//Insert the item at the mouse pointer.
				ListViewItem insertItem = (ListViewItem)dragItem.Clone();
				Slide slide = Document.Slides[dragItem.Index];

				list.Items.Insert(itemIndex, insertItem);
				Document.Slides.Insert(itemIndex, slide);
				Debug.WriteLine("Inserting");
				Document.DebugDump();

				//Removes the item from the initial location while 
				//the item is moved to the new location.
				Document.Slides.RemoveAt(dragItem.Index);
				list.Items.Remove(dragItem);
			}
			Debug.WriteLine("Finished Drop");
			Document.DebugDump();
			SetModified(true);
//			list.FixBrokenIndexes();
		}

		private void list_DragOver(object sender, DragEventArgs e) {
			bool scrollUp = false;
			int scrollCount = 0;
			Point cp = list.PointToClient(new Point(e.X, e.Y));

			ListViewItem lviHover = list.GetItemAt(cp.X, cp.Y);

			if (cp.Y <= thumbSize.Height/4) {
				scrollUp = true;
				scrollCount = 1;
			} else if (cp.Y >= list.ClientSize.Height - thumbSize.Height / 4)
				scrollCount = 1;

			if (scrollCount != 0) {
				for (int i = 0; i < scrollCount; i++)
					Win32Stuff.SendMessage(list.Handle, Win32Stuff.WM_VSCROLL, (IntPtr)(scrollUp ? Win32Stuff.SB_LINEUP : Win32Stuff.SB_LINEDOWN), IntPtr.Zero);
				list.Update();
			}

			ListViewItem dragToItem = list.GetItemAt(cp.X, cp.Y);
			if ( list.SelectedItems.Contains(dragToItem) ) {
				e.Effect = DragDropEffects.None;
				list.InsertionPoint = -1;
				return;
			}
			e.Effect = DragDropEffects.Move;
			if (dragToItem == null)
				return;
			list.InsertionPoint = dragToItem.Index;
		}

		private void list_DragLeave(object sender, EventArgs e) {
			list.InsertionPoint = -1;
		}

		private void list_GiveFeedback(object sender, GiveFeedbackEventArgs e) {

		}

		/// <summary>
		/// Handles the SelectedIndexChanged event of the list control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void list_SelectedIndexChanged(object sender, EventArgs e) {
			updateScriptBox();
		}

		/// <summary>
		/// Handles the KeyDown event of the list control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.Windows.Forms.KeyEventArgs"/> instance containing the event data.</param>
		private void list_KeyDown(object sender, KeyEventArgs e) {
			if (e.KeyCode == Keys.Delete || e.KeyCode == Keys.Back)
				listDeleteItems();
		}

		/// <summary>
		/// Handles the Click event of the deleteToolStripMenuItem control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void deleteToolStripMenuItem_Click(object sender, EventArgs e) {
			listDeleteItems();
		}

		/// <summary>
		/// Deletes items in the list
		/// </summary>
		private void listDeleteItems() {
			if (list.SelectedItems.Count == 0)
				return;

			foreach (ListViewItem lvi in list.SelectedItems) {
				Document.Slides.RemoveAt(lvi.Index);
				lvi.Remove();
				RemoveThumbnailToBeGenerated(lvi);
			}
			SetModified(true);
			Document.DebugDump();
		}

		/// <summary>
		/// Handles the MouseDoubleClick event of the list control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.Windows.Forms.MouseEventArgs"/> instance containing the event data.</param>
		private void list_MouseDoubleClick(object sender, MouseEventArgs e) {
			ListViewItem clickedItem = list.GetItemAt(e.X, e.Y);
			if (clickedItem == null)
				return;

			EndSlideshow();

			RunPresentation(clickedItem.Index);
		}

		/// <summary>
		/// Handles the Resize event of the list control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void list_Resize(object sender, EventArgs e) {
			list.Columns[0].Width = list.ClientSize.Width - 2;
		}
	
		/// <summary>
		/// Creates the thumb image list.
		/// </summary>
		private void CreateThumbImageList() {
			ImageList old = list_smallImages;
			list_smallImages = new ImageList();
			list_smallImages.ImageSize = thumbSize;
			list_smallImages.ColorDepth = ColorDepth.Depth24Bit;
			list.SmallImageList = list_smallImages;
			list.LargeImageList = list_smallImages;

			if (old != null)
				old.Dispose();
		}

		/// <summary>
		/// Loads the list from document.
		/// </summary>
		private void LoadListFromDocument() {
			Cursor.Current = Cursors.WaitCursor;
			list.Items.Clear();
			if (list_smallImages != null)
				list.SmallImageList.Images.Clear();
			foreach (Slide slide in Document.Slides)
				AddSlideToList(-1, slide);
			projectDirectory.Text = Document.Directory;
			updateScriptBox();
			Cursor.Current = Cursors.Default;
		}

		/// <summary>
		/// Add a Slide object to the Listview
		/// </summary>
		/// <param name="index">The index.-1 to add to end</param>
		/// <param name="slide">The slide.</param>
		private void AddSlideToList(int index, Slide slide) {
			ListViewItem lvi = new ListViewItem(slide.Filename);
			lvi.Tag = slide;
			lvi.StateImageIndex = slide.IsImage ? 0 : 1;
			lvi.ImageIndex = -1;

			AddThumbnailToBeGenerated(lvi);

			if (index == -1)
				list.Items.Add(lvi);
			else
				list.Items.Insert(index, lvi);
		}

		#endregion

		#region Document Open/Save/etc
		/// <summary>
		/// Check whether data should be saved and save it if so
		/// </summary>
		/// <returns>true if ok to continue or false to cancel</returns>
		private bool CheckSave() {
			if (!Document.Modified)
				return true;

			DialogResult result = MessageBox.Show(this, "You have not saved your presentation. Save Now?", "Presentation Changed", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1);
			if (result == DialogResult.Cancel)
				return false;
			else if (result == DialogResult.No)
				return true;
			return SaveFile();
		}

		/// <summary>
		/// Saves the file.
		/// </summary>
		/// <returns>true = saved, bool = cancelled</returns>
		private bool SaveFile() {
			if (String.IsNullOrEmpty(Document.Filename))
				return SaveFileAs();

			if (_SaveFile(Document.Filename))
				return true;

			// save failed maybe filename related, do a save as
			return SaveFileAs();
		}

		/// <summary>
		/// Saves the file as.
		/// </summary>
		/// <returns>true = saved, bool = cancelled</returns>
		private bool SaveFileAs() {
			while (true) {
				SaveFileDialog sfd = new SaveFileDialog();

				PrepareFileDialog(sfd);
				sfd.OverwritePrompt = true;

				if (sfd.ShowDialog(this) == DialogResult.Cancel)
					return false;

				if (_SaveFile(sfd.FileName)) {
					AddToMRU(sfd.FileName);
					return true;
				}
			}
		}

		/// <summary>
		/// Prepares the file dialog.
		/// </summary>
		/// <param name="fd">The fd.</param>
		private void PrepareFileDialog(FileDialog fd) {
			fd.AddExtension = true;
			fd.Filter = "Hovis Presenter Show (*.hps)|*.hps";
			fd.ValidateNames = true;
			fd.CheckPathExists = true;
		}

		/// <summary>
		/// save the file.
		/// </summary>
		/// <param name="filename">The filename.</param>
		/// <returns>return saved or not</returns>
		private bool _SaveFile(string filename) {
			try {
				IFormatter formatter = new BinaryFormatter();
				Stream stream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None);
				formatter.Serialize(stream, Document);
				stream.Close();
				Document.Filename = filename;
				SetModified(false);
			} catch (Exception ex) {
				MessageBox.Show(ex.Message, "Error Saving Settings", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
				return false;
			}
			return true;
		}

		/// <summary>
		/// News the presentation.
		/// </summary>
		/// <returns></returns>
		public bool NewPresentation() {
			Document = new HovisDocument();
			SetTitle();

			if (!SaveFileAs())
				return false;

			CancelBackgroundThumbnailLoad(true);
			CreateThumbImageList();
			LoadListFromDocument();
			return true;
		}

		/// <summary>
		/// Handles the Click event of the openToolStripMenuItem control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void openToolStripMenuItem_Click(object sender, EventArgs e) {
			if (!CheckSave())
				return;

			OpenFile();
		}

		/// <summary>
		/// Opens the file.
		/// </summary>
		/// <returns></returns>
		public bool OpenFile() {
			OpenFileDialog ofd = new OpenFileDialog();
			PrepareFileDialog(ofd);
			ofd.Multiselect = false;
			ofd.CheckFileExists = true;

			if (ofd.ShowDialog(this) == DialogResult.Cancel)
				return false;

			return OpenFile(ofd.FileName);
		}

		/// <summary>
		/// Opens the file.
		/// </summary>
		/// <param name="filename">The filename.</param>
		/// <returns></returns>
		public bool OpenFile(string filename) {
			try {
				IFormatter formatter = new BinaryFormatter();
				Stream stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
				Document = (HovisDocument)formatter.Deserialize(stream);
				stream.Close();
				Document.Filename = filename;
				SetModified(false);
				AddToMRU(filename);
				CancelBackgroundThumbnailLoad(true);
				CreateThumbImageList();
				LoadListFromDocument();
				return true;
			} catch (Exception ex) {
				MessageBox.Show(ex.Message, "Error Loading Presentation", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
				return false;
			}
		} 
		
		/// <summary>
		/// Sets the modified flag
		/// </summary>
		/// <param name="modified">if set to <c>true</c> [modified].</param>
		public void SetModified(bool modified) {
			Document.Modified = modified;
			SetTitle();
		}

		/// <summary>
		/// Sets the title depending on document and and status of modification
		/// </summary>
		public void SetTitle() {
			String newTitle = Title;
			if (!String.IsNullOrEmpty(Document.Filename))
				newTitle += " - " + Path.GetFileName(Document.Filename);
			if (Document.Modified)
				newTitle += " *";
			Text = newTitle;
		}		
		#endregion

		#region Menu Handling
		/// <summary>
		/// Handles the Click event of the saveToolStripMenuItem control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void saveToolStripMenuItem_Click(object sender, EventArgs e) {
			SaveFile();
			SetTitle();
		}

		/// <summary>
		/// Handles the Click event of the exitToolStripMenuItem control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void exitToolStripMenuItem_Click(object sender, EventArgs e) {
			OpeningForm.exit_Click(null, null);
		}

		/// <summary>
		/// Generates the MRU.
		/// </summary>
		private void GenerateMRU() {
			System.Collections.Specialized.StringCollection recentFiles = Properties.Settings.Default.RecentFiles;
			if (recentFiles == null)
				return;
			recentFilesToolStripMenuItem.DropDownItems.Clear();
			ToolStripMenuItem[] items = new ToolStripMenuItem[recentFiles.Count];
			int menuItemNumber = 0;
			for (int i = recentFiles.Count - 1; i >= 0; i--) {
				string file = recentFiles[i];
				ToolStripMenuItem item = new ToolStripMenuItem();
				item.Name = "mru" + menuItemNumber;
				item.Text = (menuItemNumber + 1) + " " + file;
				item.Tag = file;
				item.Click += new EventHandler(mru_Click);
				items[menuItemNumber++] = item;
			}
			recentFilesToolStripMenuItem.DropDownItems.AddRange(items);
		}

		/// <summary>
		/// Handles the Click event of the MRU.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		void mru_Click(object sender, EventArgs e) {
			ToolStripMenuItem item = (ToolStripMenuItem)sender;
			OpenFile((String)item.Tag);
		}

		/// <summary>
		/// Adds to MRU.
		/// </summary>
		/// <param name="filename">The filename.</param>
		private void AddToMRU(string filename) {
			if (Properties.Settings.Default.RecentFiles == null)
				Properties.Settings.Default.RecentFiles = new System.Collections.Specialized.StringCollection();

			if (Properties.Settings.Default.RecentFiles.Contains(filename))
				Properties.Settings.Default.RecentFiles.Remove(filename);
			else {
				if (Properties.Settings.Default.RecentFiles.Count >= 8)
					Properties.Settings.Default.RecentFiles.RemoveAt(0);
			}
			Properties.Settings.Default.RecentFiles.Add(filename);
			GenerateMRU();
		}

		/// <summary>
		/// Handles the Click event of the closeToolStripMenuItem control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void closeToolStripMenuItem_Click(object sender, EventArgs e) {
			if (!CheckSave())
				return;
			CancelBackgroundThumbnailLoad(true);
			SetModified(false);

			OpeningForm.Show();
			Hide();
			OpeningForm.Activate();
			CreateThumbImageList();	// this kills the old image list
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void runToolStripMenuItem_Click(object sender, EventArgs e) {
			RunPresentation(0);
		}

		/// <summary>
		/// Handles the Click event of the aboutToolStripMenuItem control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void aboutToolStripMenuItem_Click(object sender, EventArgs e) {
			AboutBox ab = new AboutBox();
			ab.ShowDialog(this);
			ab.Dispose();
		}

		/// <summary>
		/// Handles the Click event of the saveAsToolStripMenuItem control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void saveAsToolStripMenuItem_Click(object sender, EventArgs e) {
			SaveFileAs();
		}

		/// <summary>
		/// Handles the Click event of the optionsToolStripMenuItem control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void optionsToolStripMenuItem_Click(object sender, EventArgs e) {
			Options options = new Options();
			options.ScriptFont = ScriptFont;
			if (options.ShowDialog(this) == DialogResult.OK) {
				Size tsize = GetThumbnailSize(options.thumbSize.SelectedIndex);
				if (tsize != thumbSize) {
					Properties.Settings.Default.ThumbnailSize = options.thumbSize.SelectedIndex;
					ChangeThumbSize(tsize);
				}
				ScriptFont = options.ScriptFont;
				Properties.Settings.Default.ScriptFont = options.ScriptFont;
				Properties.Settings.Default.PresenterWindowLayout = (int) (PresenterLayout = options.PresenterWindowLayout);
			}
		}

		/// <summary>
		/// Handles the Click event of the endPresentationMenuItem control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void endPresentationMenuItem_Click(object sender, EventArgs e) {
			EndSlideshow();
		}
		#endregion

		/// <summary>
		/// handle keys send by video windows (or this one)
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="key">The key.</param>
		public void HandleKeys(object sender, Keys key) {

			// limited for slaves
			if (serverConnection != null && key != Properties.Settings.Default.KeyEndSlideshow && 
												key!=Properties.Settings.Default.KeyAltEndSlideshow )
				return;

			if ( key == Properties.Settings.Default.KeyNextSlide || key == Properties.Settings.Default.KeyAltNextSlide )
					ChangeSlideDelta(1);
			else if ( key == Properties.Settings.Default.KeyPreviousSlide || key == Properties.Settings.Default.KeyAltPreviousSlide)
					ChangeSlideDelta(-1);
			else if ( key==Properties.Settings.Default.KeyEndSlideshow || key==Properties.Settings.Default.KeyAltEndSlideshow)
					EndSlideshow();
			else if (key ==Properties.Settings.Default.KeyFirstSlide || key == Properties.Settings.Default.KeyAltFirstSlide)
					ChangeSlide(0);
			else if (key==Properties.Settings.Default.KeyLastSlide || key == Properties.Settings.Default.KeyAltLastSlide)
					ChangeSlide(Document.Slides.Count - 1);
			else if (key==Properties.Settings.Default.KeyToggleScript || key==Properties.Settings.Default.KeyAltToggleScript) {
				if (presenterWindow != null && !presenterWindow.IsDisposed)
					presenterWindow.ToggleScriptHidden();
			}
		}

		/// <summary>
		/// Stops the slideshow.
		/// </summary>
		protected void EndSlideshow() {
			endPresentationMenuItem.Visible = true;
			if (presentationWindow != null) {
				presentationWindow.StopWithWait();
				presentationWindow.Hide();
			}
			if (presenterWindow != null) {
				presenterWindow.StopWithWait();
				presenterWindow.Hide();
			}
			CloseNetworkConnections();
			endPresentationMenuItem.Visible = false;
			Visible = true;
			Activate();
			Win32Stuff.SetWindowPos(Handle, Win32Stuff.HWND_TOP, 0, 0, 0, 0, Win32Stuff.SWP_NOMOVE | Win32Stuff.SWP_NOSIZE);
			RunBackgroundImageLoader();
		}

		private delegate void StopPlayingDelegate();

		/// <summary>
		/// Stops the videos playing
		/// </summary>
		protected void StopPlaying() {
			if (InvokeRequired) {
				BeginInvoke(new StopPlayingDelegate(StopPlaying));
				return;
			}
			if (presentationWindow != null)
				presentationWindow.Stop();
			if (presenterWindow != null) 
				presenterWindow.Stop();
		}

		/// <summary>
		/// Change current slide by a particular value
		/// </summary>
		/// <param name="delta">The delta of the slide number.</param>
		protected void ChangeSlideDelta(int delta) {
			SlideNumber = LimitSlideIndex(SlideNumber + delta);
			ChangeSlide(SlideNumber);
		}

		/// <summary>
		/// Bounds a slide index to the max value of the slide list
		/// </summary>
		/// <param name="index">The index.</param>
		/// <returns></returns>
		protected int LimitSlideIndex(int index) {
			index = Math.Max(index, 0);
			index = Math.Min(index, Document.Slides.Count - 1);
			return index;
		}

		/// <summary>
		/// Changes the slide.
		/// </summary>
		/// <param name="slideNo">The slide no.</param>
		protected void ChangeSlide(int slideNo) {
			if (Document.Slides.Count == 0)
				return;

			Slide slide = Document.Slides[LimitSlideIndex(slideNo)];
			Slide nextSlide = Document.Slides[LimitSlideIndex(slideNo + 1)];

			SlideNumber = slideNo;

			string script = slide.Script;
			if (nextSlide != slide)
				script += "\r\n" + nextSlide.IntroScript;

			// change slide on any slaves first
			if (slaveController != null)
				slaveController.SendChangeSlide(slide.Filename, nextSlide.PreviewImageFilename, script);

			ChangeSlide( slide.Filename, nextSlide.PreviewImageFilename, script );
		}

		/// <summary>
		/// Replays the last slide.
		/// </summary>
		protected void ReplayLastSlide() {
			if (lastSlide == null)
				return;

			ChangeSlide(lastSlide.Filename, lastSlide.NextFilename, lastSlide.Script);
		}

		private delegate void ChangeSlideDelegate(string currentFilename, string nextFilenameOrPreview, string script);

		/// <summary>
		/// Changes the slide.
		/// </summary>
		/// <param name="currentFilename">The current filename.</param>
		/// <param name="nextFilenameOrPreview">The next filename or preview.</param>
		/// <param name="script">The script.</param>
		public void ChangeSlide(string currentFilename, string nextFilenameOrPreview, string script) {
			if (InvokeRequired) {
				Invoke(new ChangeSlideDelegate(ChangeSlide), new object[] { currentFilename,nextFilenameOrPreview, script });
				return;
			}

			lastSlide = new LastSlide(currentFilename, nextFilenameOrPreview, script);

			// Stop the videos playing locally. remotely will be done with 
			// the changeslide network message which ends up here anyway.
			if (presentationWindow != null)
				presentationWindow.Stop();
			if (presenterWindow != null)
				presenterWindow.Stop();

			// Clear any notifications
			if (presentationWindow != null)
				presentationWindow.ClearNotifications();
			if (presenterWindow != null)
				presenterWindow.ClearNotifications();

			// change slide on the local presentation window next
			if (presentationWindow != null && presentationWindow.Visible)
				presentationWindow.ChangeSlide(currentFilename, nextFilenameOrPreview, "", true);

			// now change presenter
			if (presenterWindow != null && presenterWindow.Visible)
				presenterWindow.ChangeSlide(currentFilename, nextFilenameOrPreview, script, !RunPresentationOptions.ShowScriptByDefault);
		}

		/// <summary>
		/// Opens the presentation view (if required), closes it if so.
		/// </summary>
		private void OpenPresentationView() {
			if (RunPresentationOptions.PresentationMonitor != Monitor.noMonitor) {
				if ( !IsPresentationWindowOpen ) {
					presentationWindow = new ViewForm(this, false, "Local Presentation");
					presentationWindow.Owner = this;
				}
				presentationWindow.Show(RunPresentationOptions.IsUsingFullscreen,
										RunPresentationOptions.PresentationMonitor);
			}
		}

		/// <summary>
		/// Determines whether [is presentation window open].
		/// </summary>
		/// <returns>
		/// 	<c>true</c> if [is presentation window open]; otherwise, <c>false</c>.
		/// </returns>
		public bool IsPresentationWindowOpen {
			get { return presentationWindow != null && !presentationWindow.IsDisposed && presentationWindow.Visible; }
		}

		/// <summary>
		/// Opens the Presenter view (if required), closes it if so.
		/// </summary>
		private void OpenPresenterView() {
			if (RunPresentationOptions.PresenterMonitor != Monitor.noMonitor) {
				if (!IsPresenterWindowOpen) {
					presenterWindow = new ViewForm(this, true, "Presenter");
					presenterWindow.Owner = this;
				}
				presenterWindow.Show(RunPresentationOptions.IsUsingFullscreen,
										RunPresentationOptions.PresenterMonitor);
			}
		}

		/// <summary>
		/// Determines whether [is Presenter window open].
		/// </summary>
		/// <returns>
		/// 	<c>true</c> if [is Presenter window open]; otherwise, <c>false</c>.
		/// </returns>
		public bool IsPresenterWindowOpen {
			get { return presenterWindow != null && !presenterWindow.IsDisposed && presenterWindow.Visible; }
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="startSlide"></param>
		private void RunPresentation(int startSlide) {
			RunForm runForm;
			using (runForm = new RunForm()) {
				DialogResult result = runForm.ShowDialog(this);
				if (result == DialogResult.Cancel)
					return;
				RunPresentationOptions = runForm.Options;
				RunPresentationOptions.Save();
			}

			DisableScreenSaverAndPower();
			lastSlide = null;
			OpenPresentationView();
			OpenPresenterView();

			// hook presentation window errors to the presenter view
			if (presentationWindow != null && presenterWindow!=null && !presenterWindowPlayerMessageEventAdded) {
				presentationWindow.VideoPlayer.DSVideoMessage += new DSVideoMessageEventHandler(PresentationWindow_DSVideoMessage);
				presenterWindowPlayerMessageEventAdded = true;
			}

			if (RunPresentationOptions.NetworkMode == NetworkMode.Slave)
				OpenSlaveConnection();
			else if (RunPresentationOptions.NetworkMode == NetworkMode.LocalOrMaster)
				OpenMasterConnection();

			endPresentationMenuItem.Visible = true;

			SlideNumber = startSlide;
			ChangeSlide(startSlide);
		}

		/// <summary>
		/// Handles the DSVideoMessage event of the PresentationWindow control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="HovisPresent.DSVideoMessageEventArgs"/> instance containing the event data.</param>
		void PresentationWindow_DSVideoMessage(object sender, DSVideoMessageEventArgs e) {
			// pass down to the presenter window if we've got it
			if (presenterWindow != null)
				presenterWindow.DSVideoMessageHandler(sender, e);

			// notify master
			if (serverConnection == null)
				return;

			if (e.Status == DSVideoMessageStatus.Complete) {
				serverConnection.SendMessage(SlaveMessage.Complete);
			} else if (e.Status == DSVideoMessageStatus.Error) {
				serverConnection.SendError(e.Message);
			}
		}

		/// <summary>
		/// Are any of the presentation windows open?
		/// </summary>
		/// <returns></returns>
		private bool ArePresentationWindowsOpen {
			get { return IsPresentationWindowOpen || IsPresenterWindowOpen; }
		}

		/// <summary>
		/// Handles the FormClosing event of the MainWindow control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.Windows.Forms.FormClosingEventArgs"/> instance containing the event data.</param>
		private void MainWindow_FormClosing(object sender, FormClosingEventArgs e) {
			if (e.CloseReason == CloseReason.UserClosing) {
				e.Cancel = true;
				EndSlideshow();
				closeToolStripMenuItem_Click(null, null);
				return;
			}

			CancelBackgroundThumbnailLoad(true);
			Properties.Settings.Default.MainWindowSize = ClientSize;

			if (presentationWindow != null) {
				presentationWindow.Close();
				presentationWindow.Dispose();
			}

			if (presenterWindow != null) {
				presenterWindow.Close();
				presenterWindow.Dispose();
			}

			if (!CheckSave()) {
				e.Cancel = true;
				return;
			}
			Properties.Settings.Default.Save();
			RestoreScreenSaverAndPower();
		}	

		#region Screen Saver and Power Functions

		/// <summary>
		/// Gets the screen saver and power info.
		/// </summary>
		private void GetScreenSaverAndPowerInfo() {
			if (ScreenPowerSavingChecked)
				return;

			uint value=0;

			Win32Stuff.SystemParametersInfo(Win32Stuff.SPI_GETLOWPOWERACTIVE, 0, ref value, 0);
			ScreenLowPowerEnabled = value != 0;

			Win32Stuff.SystemParametersInfo(Win32Stuff.SPI_GETPOWEROFFACTIVE, 0, ref value, 0);
			ScreenPowerOffEnabled = value != 0;

			Win32Stuff.SystemParametersInfo(Win32Stuff.SPI_GETSCREENSAVEACTIVE, 0, ref value, 0);
			ScreenSaverEnabled = value != 0;

			ScreenPowerSavingChecked = true;
		}

		/// <summary>
		/// Disables the screen saver and power.
		/// </summary>
		private void DisableScreenSaverAndPower() {
			GetScreenSaverAndPowerInfo();

			Win32Stuff.SystemParametersInfo(Win32Stuff.SPI_SETLOWPOWERACTIVE, 0, 0, 0);
			Win32Stuff.SystemParametersInfo(Win32Stuff.SPI_SETPOWEROFFACTIVE, 0, 0, 0);
			Win32Stuff.SystemParametersInfo(Win32Stuff.SPI_SETSCREENSAVEACTIVE, 0, 0, 0);
		}

		/// <summary>
		/// Restores the screen saver and power.
		/// </summary>
		private void RestoreScreenSaverAndPower() {
			if (!ScreenPowerSavingChecked)
				return;

			Win32Stuff.SystemParametersInfo(Win32Stuff.SPI_SETLOWPOWERACTIVE, 0, (uint)(ScreenLowPowerEnabled ? 1 : 0), 0);
			Win32Stuff.SystemParametersInfo(Win32Stuff.SPI_SETPOWEROFFACTIVE, 0, (uint)(ScreenPowerOffEnabled ? 1 : 0), 0);
			Win32Stuff.SystemParametersInfo(Win32Stuff.SPI_SETSCREENSAVEACTIVE, 0, (uint)(ScreenSaverEnabled ? 1 : 0), 0);
		}

		#endregion

	#region Network
		#region Slave
		/// <summary>
		/// Opens the slave connection.
		/// </summary>
		protected void OpenSlaveConnection() {
			if (presentationWindow != null)
				presentationWindow.ShowNetDisconnected(true);

			serverConnection = new ServerConnection();
			serverConnection.OnServerConnected += new ServerConnected(OnServerConnected);
			serverConnection.OnServerClientClosed += new ServerClientClosed(OnServerClientClosed);
			serverConnection.OnServerClientMessageRecieved += new ServerClientMessageRecieved(OnServerClientMessageRecieved);
			serverConnection.Listen(RunPresentationOptions.NetworkPort);
		}

		/// <summary>
		/// Called when [server client message recieved].
		/// </summary>
		/// <param name="server">The server.</param>
		/// <param name="message">The message.</param>
		/// <param name="parameters">The parameters.</param>
		void OnServerClientMessageRecieved(ServerConnection server, MasterMessage message, string[] parameters) {
#if DEBUG
			string paramtext = "";
			for (int i = 1; i < parameters.Length; i++) {
				paramtext += "*" + parameters[i] + "* ";
			}

			Debug.WriteLine("Message recieved from master: "+message+" ("+paramtext+")" );
#endif

			switch (message) {
				case MasterMessage.ChangeSlide:
					if (parameters.Length!=4) {
						serverConnection.SendInvalidParameterError();
						return;
					}
					ChangeSlide(parameters[1],parameters[2],parameters[3]);
					break;

				case MasterMessage.Stop:
					StopPlaying();
					break;

				case MasterMessage.IsAlive:
					serverConnection.SendYesAlive();
					break;
			}
		}

		/// <summary>
		/// Called when [server client closed].
		/// </summary>
		/// <param name="server">The server.</param>
		void OnServerClientClosed(ServerConnection server) {
			Debug.WriteLine("Master Disconnected");
			if (presentationWindow != null)
				presentationWindow.ShowNetDisconnected(true);
		}

		/// <summary>
		/// Called when [server connected].
		/// </summary>
		/// <param name="server">The server.</param>
		void OnServerConnected(ServerConnection server) {
			Debug.WriteLine("Master Connected");
			if (presentationWindow != null)
				presentationWindow.ShowNetDisconnected(false);

			if (lastSlide != null)
				serverConnection.SendMessage(SlaveMessage.Hello, lastSlide.Filename);
			else
				serverConnection.SendMessage(SlaveMessage.Hello, "");
		}
		#endregion

		#region Master
		/// <summary>
		/// Opens the master connection.
		/// </summary>
		protected void OpenMasterConnection() {
			if (RunPresentationOptions.Slaves == null || RunPresentationOptions.Slaves.Count == 0)
				return;
			slaveController = new SlaveController(RunPresentationOptions.NetworkPort);
			slaveController.OnSlaveClosed += new SlaveClosed(OnSlaveClosed);
			slaveController.OnSlaveMessageReceived += new SlaveMessageReceived(OnSlaveMessageReceived);
			slaveController.OnSlaveConnected += new SlaveConnected(OnSlaveConnected);
			slaveController.Open(RunPresentationOptions.Slaves);
		}

		/// <summary>
		/// Called when [slave connected].
		/// </summary>
		/// <param name="host">The host.</param>
		void OnSlaveConnected(string host) {
			SlaveConnectionStatusChanged(host, true);
		}

		/// <summary>
		/// Called when [slave message recieved].
		/// </summary>
		/// <param name="host">The host.</param>
		/// <param name="message">The message.</param>
		/// <param name="parameters">The parameters.</param>
		void OnSlaveMessageReceived(string host, SlaveMessage message, string[] parameters) {
			if (InvokeRequired) {
				BeginInvoke(new SlaveMessageReceived(OnSlaveMessageReceived), new object[] { host, message, parameters });
				return;
			}

			if (presenterWindow != null) 
				presenterWindow.SignalErrorOnCurrent("Host " + host, ""+message+" "+(parameters.Length>1 ? parameters[1] : "") );

			// we've connected to a slave. determine what we do to match local status.
			if (slaveController!=null && lastSlide!=null && message == SlaveMessage.Hello) {
				string slavePlaying = parameters.Length>2 ? parameters[1] : "";

				// we both on the same thing? do nothing.
				if (lastSlide.Filename == slavePlaying)
					return;

				// React differently if we are just running one slave.
				if (slaveController.SlaveCount == 1) {
					slaveController.SendChangeSlide(host, lastSlide.Filename, lastSlide.NextFilename, lastSlide.Script);
					ReplayLastSlide();
				} else {
					// just stop it. we'll have to catch up later.
					slaveController.SendStop(host);
				}
			}
		}

		/// <summary>
		/// Called when [slave closed].
		/// </summary>
		/// <param name="host">The host.</param>
		void OnSlaveClosed(string host) {
			SlaveConnectionStatusChanged(host, false);
		}

		delegate void SlaveConnectionStatusChangedDelegate(string host, bool connected);

		/// <summary>
		/// Called when a slave's connection status changes
		/// </summary>
		/// <param name="host">The host.</param>
		/// <param name="connected">if set to <c>true</c> [connected]. or false - [disconnected]</param>
		void SlaveConnectionStatusChanged(string host, bool connected) {
			if (InvokeRequired) {
				BeginInvoke(new SlaveConnectionStatusChangedDelegate(SlaveConnectionStatusChanged), new object[] { host, connected });
				return;
			}

			if (presenterWindow != null) {
				presenterWindow.SignalErrorOnCurrent("Host " + host + " " + (connected ? "connected" : "DISCONNECTED"), null);
			}

			if (!connected) {
				Debug.WriteLine("Trying to re-establish connection with " + host);
				slaveController.RetrySlaveConnection(host);
			}

		}

		#endregion

		/// <summary>
		/// Closes the network connections.
		/// </summary>
		protected void CloseNetworkConnections() {
			if (slaveController != null)
				slaveController.Close();
			slaveController = null;

			if (serverConnection != null)
				serverConnection.Close();
			serverConnection = null;
		}

	#endregion

		#region Background Thumbnail Image Loading

		/// <summary>
		/// Gets the size of the thumbnail.
		/// </summary>
		/// <param name="sizeIndex">Index of the size.</param>
		/// <returns></returns>
		private Size GetThumbnailSize(int sizeIndex) {
			Size tsize;
			switch (sizeIndex) {
				case 0:
					tsize = new Size(100, 75);
					break;
				default:
				case 1:
					tsize = new Size(152, 114);
					break;
				case 2:
					tsize = new Size(220, 165);
					break;
				case 3:
					tsize = new Size(112, 70);
					break;
				case 4:
					tsize = new Size(144, 90);
					break;
				case 5:
					tsize = new Size(224, 140);
					break;
			}
			return tsize;
		}

		/// <summary>
		/// Changes the size of the thumb.
		/// </summary>
		/// <param name="newSize">The new size.</param>
		private void ChangeThumbSize(Size newSize) {
			thumbSize = newSize;
			CancelBackgroundThumbnailLoad(false);
			foreach (ListViewItem lvi in list.Items)
				lvi.ImageIndex = -1;
			CreateThumbImageList();
			foreach (ListViewItem lvi in list.Items)
				AddThumbnailToBeGenerated(lvi);
		}

		/// <summary>
		/// Adds the a listview item to have its thumbnail generated.
		/// </summary>
		/// <param name="lvi">The lvi.</param>
		private void AddThumbnailToBeGenerated(ListViewItem lvi) {
			lock (ImagesToLoad) {
				ImagesToLoad.Add( lvi );
				if (!backgroundImageLoader.IsBusy)
					RunBackgroundImageLoader();
			}
		}

		/// <summary>
		/// Removes the thumbnail from the to be generated list (i.e. upon deletion)
		/// </summary>
		/// <param name="lvi">The lvi.</param>
		private void RemoveThumbnailToBeGenerated(ListViewItem lvi) {
			lock(ImagesToLoad)
				ImagesToLoad.Remove(lvi);
		}

		/// <summary>
		/// finds the next thumb to load and runs the backround image loader.
		/// </summary>
		private void RunBackgroundImageLoader() {
			if (ImagesToLoad.Count == 0)
				return;
			if (backgroundImageLoader.IsBusy)
				return;

			int startIndex=0,endIndex=10000;
			ListViewItem startItem = list.FindNearestItem(SearchDirectionHint.Right, 0, 10);
			ListViewItem endItem = list.FindNearestItem(SearchDirectionHint.Left, list.ClientSize.Width, list.ClientSize.Height);

			if (startItem != null)
				startIndex = startItem.Index-1;
			if (endItem != null)
				endIndex = endItem.Index;

			BackgroundImageToLoad = null;
			foreach (ListViewItem i in ImagesToLoad) {
				if (i.Index < startIndex || i.Index>endIndex)
					continue;
				BackgroundImageToLoad = i;
				break;
			}
			if (BackgroundImageToLoad==null)
				BackgroundImageToLoad = ImagesToLoad[0];
			ImagesToLoad.Remove(BackgroundImageToLoad);

			Slide slide = (Slide) BackgroundImageToLoad.Tag;

			var param = new KeyValuePair<string, bool>(Document.Directory + "\\" + slide.PreviewImageFilename, slide.IsImage);

			try {
				backgroundImageLoader.RunWorkerAsync(param);
			} catch(Exception) {
				// failed to run. stick back in queue
				ImagesToLoad.Add(BackgroundImageToLoad);
				BackgroundImageToLoad=null;
			}
		}

		/// <summary>
		/// Loads thumb images in the background
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.ComponentModel.DoWorkEventArgs"/> instance containing the event data.</param>
		private void backgroundImageLoader_DoWork(object sender, DoWorkEventArgs e) {
			var param = (KeyValuePair<string, bool>) e.Argument;
			string filename = param.Key;
			bool isImage = param.Value;
			e.Result = GetThumbnail(e, filename, isImage);
		}

		/// <summary>
		/// Gets the thumbnail.
		/// </summary>
		/// <param name="e">The <see cref="System.ComponentModel.DoWorkEventArgs"/> instance containing the event data.</param>
		/// <param name="filename">The filename.</param>
		/// <param name="isImage">if set to <c>true</c> [is image].</param>
		private Image GetThumbnail(DoWorkEventArgs e, string filename, bool isImage) {
			Bitmap image = null, thumb = null;
			Graphics g;
			try {
				image = new Bitmap(filename);

				// calculate new image size based on 4:3 aspect ratio
				double originalAR = ((double)image.Size.Width) / ((double)image.Size.Height);
				double thumbAR = ((double)thumbSize.Width) / ((double)thumbSize.Height);
				Size currentThumbSize;
				if (originalAR > thumbAR) {
					// width > height 
					currentThumbSize = new Size(thumbSize.Width, (int)(((double)thumbSize.Width) / originalAR));
				} else {
					// height > width 
					currentThumbSize = new Size((int)(((double)thumbSize.Height) * originalAR), thumbSize.Height);
				}
				Point currentThumbDrawPoint = new Point((thumbSize.Width - currentThumbSize.Width) / 2, (thumbSize.Height - currentThumbSize.Height) / 2);

				thumb = new Bitmap(thumbSize.Width, thumbSize.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
				g = Graphics.FromImage(thumb);
				g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
				g.DrawImage(image, new Rectangle(currentThumbDrawPoint, currentThumbSize));

				int stateIndex = isImage ? 0 : 1;
				list_stateImages.Draw(g, new Point(5, thumbSize.Height - 5 - list_stateImages.ImageSize.Height), stateIndex);

				g.Dispose();
				image.Dispose();

				return thumb;
			} catch (Exception) {
				if (image != null)
					image.Dispose();
				return null;
			}
		}

		/// <summary>
		/// called when thumb image has been loaded from the background
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.ComponentModel.RunWorkerCompletedEventArgs"/> instance containing the event data.</param>
		private void backgroundImageLoader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
			Image image = (Image)e.Result;
			
			if (list.IsDisposed) {
				if (image != null)
					image.Dispose();
				return;
			}

			// if the size doesn't match the thumbsize, we've changed the thumbsize.
			// discard this.
			if (image != null && !image.Size.Equals(thumbSize)) {
				image.Dispose();
				return;
			}

			if (image != null && list_smallImages!=null && list_smallImages.HandleCreated) {
				list_smallImages.Images.Add(image);
				BackgroundImageToLoad.ImageIndex = list_smallImages.Images.Count - 1;
			}
			BackgroundImageToLoad = null;

			if (image != null)
				image.Dispose();

			// if running a presentation, pause doing stuff.
			if (ArePresentationWindowsOpen)
				return;

			RunBackgroundImageLoader();
		}

		/// <summary>
		/// Cancels the background thumbnail load.
		/// </summary>
		/// <param name="clear">if set to <c>true</c> clear the list.</param>
		private void CancelBackgroundThumbnailLoad(bool clear) {
			try {
				if (clear)
					ImagesToLoad.Clear();
				backgroundImageLoader.CancelAsync();
			} catch {
			}
		}

		/// <summary>
		/// Resumes the backround thumbnail load.
		/// </summary>
		private void ResumeBackroundThumbnailLoad() {
		}


		#endregion

	}

	/// <summary>
	/// Stores the details of the last slide shown. used by the master
	/// when slaves connect - network trouble might mean they are out of
	/// sync
	/// </summary>
	class LastSlide
	{
		internal LastSlide(string current, string next, string script) {
			Filename = current;
			NextFilename = next;
			Script = script;
		}
		/// <summary>
		/// relative to presentation directory
		/// </summary>
		internal string Filename;
		/// <summary>
		/// relative to project directory
		/// </summary>
		internal string NextFilename;
		internal bool IsVideo { get { return !Slide.IsFilenameAnImage(Filename); } }
		internal string Script;
	}

	/// <summary>
	/// Sort ListView items by index
	/// </summary>
	internal class ListViewItemSortByIndex : System.Collections.IComparer
	{
		#region IComparer Members

		public int Compare(object x, object y) {
			ListViewItem X = (ListViewItem)x;
			ListViewItem Y = (ListViewItem)y;
			return X.Index - Y.Index;
		}

		#endregion
	}
}
